package com.scheduling.schedule.algorithm.impl;

import com.scheduling.machine.entity.Machine;
import com.scheduling.order.entity.Order;
import com.scheduling.product.entity.Operation;
import com.scheduling.schedule.algorithm.ScheduleAlgorithm;
import com.scheduling.schedule.dto.ScheduleResult;
import com.scheduling.schedule.entity.ProductionTask;
import com.scheduling.schedule.entity.ScheduleDetail;
import com.scheduling.schedule.entity.Tuple2;
import com.scheduling.schedule.entity.WorkOrder;
import com.scheduling.schedule.enums.ScheduleAlgorithmType;
import com.scheduling.schedule.service.MachineLoadService;
import com.scheduling.schedule.service.OperationScheduleService;
import com.scheduling.schedule.service.ScheduleDataService;
import com.scheduling.schedule.service.WorkOrderGenerationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 紧急优先 + 机器利用率最大
 * 完整流程：
 * 1. 输入：订单列表
 * 2. 第一阶段：订单优先级设定与处理，将订单按照优先级排序
 * 3. 第二阶段：
 * 订单 → 工单生成 （依据订单项生成工单）
 * 工单 → 任务（基于 machine_load ）
 * 4. 输出：完整排产结果（ Task 集合）
 *
 * @author linjinsheng
 * @since 2025-06-04
 */
@Slf4j
@Component("COMBINE_ALGORITHM")
@RequiredArgsConstructor
public class CombineAlgorithm implements ScheduleAlgorithm {

    private final WorkOrderGenerationService workOrderGenerationService;
    private final MachineLoadService machineLoadService;
    private final ScheduleDataService scheduleDataService;
    private final OperationScheduleService operationScheduleService;

    @Override
    public ScheduleResult schedule(List<Order> orders, LocalDateTime scheduleStartTime) {
        log.info("开始执行 Combine 排产算法, 订单数量: {}", orders.size());

        long startTime = System.currentTimeMillis();
        ScheduleResult result = new ScheduleResult();

        try {
            if (orders.isEmpty()) {
                throw new IllegalArgumentException("订单列表不能为空");
            }

            //优先级逻辑设定
            log.info("优先级订单排序");
            List<Order> sortedOrders = sortWithPriority(orders);

            //订单 -> 工单
            log.info("工单生成");
            List<WorkOrder> workOrders = generateWorkOrders(sortedOrders, scheduleStartTime);

            //工单 -> 任务
            log.info("任务生成");
            List<ProductionTask> productionTasks = generateProductionTasks(workOrders, scheduleStartTime);

            //任务 -> 任务细节
            List<ScheduleDetail> scheduleDetails = generateScheduleDetails(productionTasks);

            //统计信息
            LocalDateTime scheduleEndTime = calculateScheduleEndTime(productionTasks);
            BigDecimal totalUtilization = scheduleDataService.calculateTotalUtilization(productionTasks,
                    scheduleStartTime,
                    scheduleEndTime);

            //封装结果
            result.setSuccess(true);
            result.setScheduleStartTime(scheduleStartTime);
            result.setScheduleEndTime(scheduleEndTime);
            result.setTotalUtilization(totalUtilization);
            result.setGeneratedWorkOrders(workOrders);
            result.setProductionTasks(productionTasks);
            result.setScheduleDetails(scheduleDetails);
            result.setAlgorithmType(getAlgorithmType());
            result.setAlgorithmDescription(getDescription());
            result.setTotalWorkOrders(workOrders.size());
            result.setTotalTasks(productionTasks.size());
            result.setTotalMachines(calculateMachineCount(productionTasks));
            result.setExecutionTimeMs(System.currentTimeMillis() - startTime);

            log.info("COMBINE排产算法执行完成，工单: {}, 任务: {}, 设备利用率: {}%, 执行耗时: {}ms",
                    workOrders.size(), productionTasks.size(), totalUtilization, result.getExecutionTimeMs());

        } catch (Exception e) {
            log.error("Combine 排产算法执行失败", e);
            result.setSuccess(false);
            result.setErrorMessage("算法执行失败: " + e.getMessage());
        }

        return result;
    }


    private int calculateMachineCount(List<ProductionTask> productionTasks) {
        return (int) productionTasks.stream()
                .map(ProductionTask::getMachineId)
                .filter(Objects::nonNull)
                .distinct()
                .count();
    }

    private LocalDateTime calculateScheduleEndTime(List<ProductionTask> productionTasks) {
        return productionTasks.stream()
                .map(ProductionTask::getPlanEndTime)
                .filter(Objects::nonNull)
                .max(LocalDateTime::compareTo)
                .orElse(LocalDateTime.now());
    }

    private List<ScheduleDetail> generateScheduleDetails(List<ProductionTask> productionTasks) {
        List<ScheduleDetail> details = new ArrayList<>();
        AtomicInteger sequence = new AtomicInteger(1);
        String timeStamp = String.valueOf(System.currentTimeMillis()).substring(7); // 使用毫秒时间戳后6位

        for (ProductionTask task : productionTasks) {
            ScheduleDetail detail = new ScheduleDetail();
            detail.setDetailId("COMBINE_DETAIL_" + timeStamp + String.format("%03d", sequence.getAndIncrement()));
            detail.setTaskId(task.getTaskId());
            detail.setMachineId(task.getMachineId());
            detail.setScheduledStartTime(task.getPlanStartTime());
            detail.setScheduledEndTime(task.getPlanEndTime());
            detail.setSequenceNo(sequence.get());
            detail.setCreateTime(LocalDateTime.now());

            details.add(detail);
        }

        return details;
    }

    /**
     * 使用优先级排序
     * <p>1. 为订单生成优先级(用于分辨订单的紧迫程度)</p>
     * <p>2. 依据 剩余时间 进行优先级设定, 越少越紧迫</p>
     *
     * @param orders 订单集合
     * @return 排序后的订单
     */
    private List<Order> sortWithPriority(List<Order> orders) {
        LocalDate currentDate = LocalDate.now();


        orders.sort((order1, order2) -> {
            // 计算订单1的剩余时间（交货期 - 当前日期）
            long daysRemaining1 = ChronoUnit.DAYS.between(currentDate, order1.getDueDate());
            // 计算订单2的剩余时间
            long daysRemaining2 = ChronoUnit.DAYS.between(currentDate, order2.getDueDate());

            // 按剩余时间升序排序（值越小越紧迫）
            return Long.compare(daysRemaining1, daysRemaining2);
        });

        return orders;
    }

    @Override
    public List<WorkOrder> generateWorkOrders(List<Order> orders, LocalDateTime scheduleStartTime) {
        return workOrderGenerationService.generateDefaultWorkOrders(orders, scheduleStartTime);
    }

    @Override
    public List<ProductionTask> generateProductionTasks(List<WorkOrder> workOrders, LocalDateTime scheduleStartTime) {
        List<ProductionTask> allTasks = new ArrayList<>();
        AtomicInteger taskSequence = new AtomicInteger(1);

        //获取机器负载情况
        MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machineAvailableTime =
                getMachineAvailableTime(scheduleStartTime);

        //处理工单
        for (WorkOrder workOrder : workOrders) {
            try {
                List<ProductionTask> orderTasks = createTasksForWorkOrder(workOrder, machineAvailableTime,
                        taskSequence);
                allTasks.addAll(orderTasks);

                log.debug("工单 {} 生成了 {} 个任务", workOrder.getWorkOrderId(), orderTasks.size());
            } catch (Exception e) {
                log.error("为工单 {} 生成任务失败: {}", workOrder.getWorkOrderId(), e.getMessage());
            }
        }

        log.info("Combine Algorithm 生产任务生成完成，总任务数: {}", allTasks.size());
        return allTasks;
    }

    /**
     * 依据工单生成生产任务
     *
     * @param workOrder             工单
     * @param machinesAvailableTime 机器的空闲时间 MultiValueMap
     * @param taskSequence          用于记录 task 在排产中的产生顺序(生成 taskID)
     * @return 工单对应 task 的 List
     */
    private List<ProductionTask> createTasksForWorkOrder(
            WorkOrder workOrder,
            MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime,
            AtomicInteger taskSequence
    ) {
        //工单 -> 产品 -> 工序 -> 机器 -> 机器空闲时间 -> 任务

        //获取数目
        Integer productQuantity = workOrder.getQuantity();
        //找到产品
        String productID = workOrder.getProductId();

        //找到工序
        List<Operation> operations = scheduleDataService.getOperationsByProductId(productID);
        //工序执行顺序排序（按依赖关系排序成能够顺序执行的顺序）
        operationScheduleService.sortOperations(operations);

        return createTasksForOperation(
                productQuantity,
                productID,
                operations,
                workOrder,
                machinesAvailableTime,
                taskSequence
        );
    }

    private List<ProductionTask> createTasksForOperation(
            Integer productQuantity,
            String productID,
            List<Operation> operations,
            WorkOrder workOrder,
            MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime,
            AtomicInteger taskSequence
    ) {
        List<ProductionTask> tasks = new ArrayList<>();
        //遍历工序生成任务
        LocalDateTime predecessorOpEndTime = LocalDateTime.MIN;
        for (Operation operation : operations) {
            //更新前置工序的结束时间
            //存在前置工序
            if (operation.getPredecessorOps() != null) {
                //更新变量：前置工序的结束时间
                String[] predecessorOps = operation.getPredecessorOps().split(",");
                for (String predecessorOpId : predecessorOps) {
                    Optional<ProductionTask> predTask = tasks.stream()
                            .filter(task -> Objects.equals(task.getOperationId(), predecessorOpId))
                            .findFirst();

                    if (predTask.isPresent()) {
                        LocalDateTime currentTime = predTask.get().getPlanEndTime();
                        if (currentTime.isAfter(predecessorOpEndTime)) {
                            predecessorOpEndTime = currentTime;
                        }
                    }
                }
            }
            //找到需要的机器类型
            String machineTypeId = operation.getMachineTypeId();
            //找到对应的机器集合
            List<Machine> machines = scheduleDataService.getMachinesByType(machineTypeId);
            //找到最早能插入的时间段以及对应的机器
            Tuple2<String, Tuple2<LocalDateTime, LocalDateTime>> selectedInfo = findEarliestInterval(
                    machines,
                    machinesAvailableTime,
                    predecessorOpEndTime,
                    productQuantity
            );
            String selectedMachineId = selectedInfo.getFirst();
            Tuple2<LocalDateTime, LocalDateTime> selectedInterval = selectedInfo.getSecond();

            //找到所有机器上最早能插入的时间，生成任务
            assert selectedInterval != null;
            ProductionTask task = createTask(
                    selectedInterval,
                    taskSequence,
                    workOrder,
                    operation,
                    productQuantity,
                    selectedMachineId
            );
            tasks.add(task);

            log.info("为工单 {} 对应产品 {} 对应工序 {} 分配机器 {} 分配时间 {}--{}",
                    task.getWorkOrderId(),
                    productID,
                    task.getOperationId(),
                    task.getMachineId(),
                    selectedInterval.getFirst(),
                    selectedInterval.getSecond()
            );
            //更新机器空闲时间表
            updateMachineFreeTimes(
                    machinesAvailableTime,
                    selectedMachineId,
                    task.getPlanStartTime(),
                    task.getPlanEndTime()
            );
        }
        return tasks;
    }


    private Tuple2<String, Tuple2<LocalDateTime, LocalDateTime>> findEarliestInterval(
            List<Machine> machines,
            MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime,
            LocalDateTime predecessorOpEndTime,
            Integer productQuantity
    ) {
        String selectedMachineId = null;
        Tuple2<LocalDateTime, LocalDateTime> selectedInterval = null;
        //使用机器集合中的机器ID 在 机器空闲时间表中遍历找到最早能排产的时间段
        for (Machine machine : machines) {
            //计算任务的生产时间 以半小时为基本单位 向上取整
            double machineCapacity = machine.getMaxCapacity().doubleValue();
            double hoursRequired = productQuantity / machineCapacity;
            double halfHourUnits = Math.ceil(hoursRequired * 2);
            double totalHours = halfHourUnits * 0.5;
            Duration duration = Duration.ofMinutes((long) (totalHours * 60));

            //找到当前机器的负载情况
            String machineId = machine.getMachineId();
            List<Tuple2<LocalDateTime, LocalDateTime>> freeTimes = machinesAvailableTime.get(machineId);
            freeTimes.sort(Comparator.comparing(Tuple2::getFirst));//升序排序
            //遍历所有时间段进行插入，找到最早能排产的时间段
            for (Tuple2<LocalDateTime, LocalDateTime> freeTime : freeTimes) {
                LocalDateTime freeStart = freeTime.getFirst();
                LocalDateTime freeEnd = freeTime.getSecond();

                //如果前置工序的结束时间在当前 free 时间段之后，则无法分配，找下一个时间段
                if (freeEnd.isBefore(predecessorOpEndTime)) continue;

                //如果空闲时间的开始 在前置工序结束时间之前，时间段应是 [predOpEndTime, freeEnd]
                if (freeStart.isBefore(predecessorOpEndTime)) {
                    freeStart = predecessorOpEndTime;
                }

                //计算free时间
                Duration freeDuration;
                if (freeEnd.equals(LocalDateTime.MAX)) {
                    freeDuration = Duration.ofSeconds(Long.MAX_VALUE);
                } else {
                    freeDuration = Duration.between(freeStart, freeEnd);
                }

                //如果能插入
                if (freeDuration.compareTo(duration) >= 0) {
                    LocalDateTime taskEnd = freeStart.plusMinutes(duration.toMinutes());
                    Tuple2<LocalDateTime, LocalDateTime> allocateInterval = new Tuple2<>(
                            freeStart,
                            taskEnd
                    );
                    //第一次赋初值
                    if (selectedMachineId == null) {
                        selectedMachineId = machineId;
                        selectedInterval = allocateInterval;
                    }
                    //若在这台机器上更早结束
                    if (taskEnd.isBefore(selectedInterval.getSecond())) {
                        selectedMachineId = machineId;
                        selectedInterval = allocateInterval;
                    } else if (taskEnd.isEqual(selectedInterval.getSecond())) {
                        //结束时间相同看持续时间，若都相同就选先选的
                        if (freeStart.isAfter(allocateInterval.getFirst())) {
                            selectedMachineId = machineId;
                            selectedInterval = allocateInterval;
                        }
                    }
                    break; // 当前机器已经找到,继续下个机器
                }
            }
        }
        return new Tuple2<>(selectedMachineId, selectedInterval);
    }


    private ProductionTask createTask(
            Tuple2<LocalDateTime, LocalDateTime> selectedInterval,
            AtomicInteger taskSequence,
            WorkOrder workOrder,
            Operation operation,
            Integer productQuantity,
            String selectedMachineId
    ) {
        ProductionTask task = new ProductionTask();
        String timeStamp = String.valueOf(System.currentTimeMillis()).substring(7); // 使用毫秒时间戳后6位
        Duration productDuration = Duration.between(selectedInterval.getFirst(), selectedInterval.getSecond());
        LocalDateTime taskStart = selectedInterval.getFirst();
        LocalDateTime taskEnd = selectedInterval.getSecond();
        task.setTaskId("COMBINE_" + timeStamp + String.format("%03d", taskSequence.getAndIncrement()));
        task.setWorkOrderId(workOrder.getWorkOrderId());
        task.setOperationId(operation.getOperationId());
        task.setMachineId(selectedMachineId);
        task.setQuantity(productQuantity);
        task.setStatus("已分配");
        task.setStandardTime((int) productDuration.toMinutes());
        task.setPlannedTime((int) productDuration.toMinutes());
        // 设置时间
        task.setPlanStartTime(taskStart);
        task.setPlanEndTime(taskEnd);
        task.setCreateTime(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());

        return task;
    }

    private void updateMachineFreeTimes(MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime, String selectedMachineId, LocalDateTime taskStart, LocalDateTime taskEnd) {
        List<Tuple2<LocalDateTime, LocalDateTime>> machineFreeTime =
                machinesAvailableTime.get(selectedMachineId);
        Iterator<Tuple2<LocalDateTime, LocalDateTime>> iterator = machineFreeTime.iterator();

        List<Tuple2<LocalDateTime, LocalDateTime>> newSlots = new ArrayList<>();

        log.info("机器 {} 空闲时间集合为", selectedMachineId);
        for (Tuple2<LocalDateTime, LocalDateTime> freeInter : machineFreeTime) {
            log.info("{} ----- {}", freeInter.getFirst(), freeInter.getSecond());
        }

        while (iterator.hasNext()) {
            Tuple2<LocalDateTime, LocalDateTime> slot = iterator.next();
            LocalDateTime slotStart = slot.getFirst();
            LocalDateTime slotEnd = slot.getSecond();

            // 检查任务是否完全包含在空闲时间段内
            if (!taskStart.isBefore(slotStart) && !taskEnd.isAfter(slotEnd)) {
                // 移除当前时间段，因为将被部分占用
                iterator.remove();

                // 保留左侧空闲时间段（如果存在）
                if (slotStart.isBefore(taskStart)) {
                    newSlots.add(new Tuple2<>(slotStart, taskStart));
                }

                // 保留右侧空闲时间段（如果存在）
                if (taskEnd.isBefore(slotEnd)) {
                    newSlots.add(new Tuple2<>(taskEnd, slotEnd));
                }
                break;
            }
        }
        // 添加新空闲段并确保有序
        machineFreeTime.addAll(newSlots);
        machineFreeTime.sort(Comparator.comparing(Tuple2::getFirst));

        log.info("更新 机器 {} 空闲时间集合为 {}", selectedMachineId, machineFreeTime);
    }


    /**
     * 访问 Service 来获取所有机器的可用时间表
     *
     * @param scheduleStartTime 排产的开始时间
     * @return MultiValueMap[machineId, freeTime[start, end]]
     */
    private MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> getMachineAvailableTime(LocalDateTime scheduleStartTime) {
        // 获取机器当前负荷情况
        return machineLoadService.getCurrentMachineAvailablePeriod(scheduleStartTime);
    }

    /**
     * 获取算法类型
     *
     * @return AlgorithmType
     */
    @Override
    public String getAlgorithmType() {
        return ScheduleAlgorithmType.COMBINE_ALGORITHM.getCode();
    }

    /**
     * 获取算法描述
     *
     * @return description
     */
    @Override
    public String getDescription() {
        return ScheduleAlgorithmType.COMBINE_ALGORITHM.getDescription();
    }
}
